use std::error::Error;

#[derive(Debug)]
struct InternalError;

impl std::fmt::Display for InternalError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "")
    }
}

impl std::error::Error for InternalError {}

#[derive(Debug)]
pub enum ErrorKind {
    NoKindEasyError,
    EndedEarly,
    ParsingFailed,
    EncodingError,
    IoError(std::io::Error),
}

#[derive(Debug)]
pub struct EasyError {
    kind: ErrorKind,
    reason: &'static str,
    error: Option<InternalError>,
}

impl From<&'static str> for EasyError {
    fn from(reason: &'static str) -> Self {
        EasyError{
            kind: ErrorKind::StaticError,
            reason,
            error: None
        }
    }
}

impl From<String> for EasyError {
    fn from(reason: String) -> Self {
        EasyError{
            kind: ErrorKind::StaticError,
            reason: reason.as_str(),
            error: None
        }
    }
}

impl From<::std::string::FromUtf8Error> for EasyError {
    fn from(_e: ::std::string::FromUtf8Error) -> Self {
        EasyError{
            kind: ErrorKind::StaticError,
            reason: format!("could not parse string as UTF8: {}", _e).as_str(),
            error: None
        }
    }
}

impl<E: std::fmt::Debug + Clone> From<nom::Err<E>> for EasyError {
    fn from(e: nom::Err<E>) -> Self {
        let mut error_kind :ErrorKind = ErrorKind::NoKindEasyError;
        let mut error_reason: &str = "";
        let mut error : Option<InternalError> = None;
        match e {
            nom::Err::Incomplete(ref needed) => {
                error_kind = ErrorKind::EndedEarly;
                match needed {
                    nom::Needed::Unknown => error_reason = "stream ended early",
                    nom::Needed::Size(size) => error_reason =format!("unexpectedly ended early: expected {} bytes", *size).as_str()
                }
            },
            nom::Err::Error(ctx) | nom::Err::Failure(ctx)  => {
                error_kind = ErrorKind::ParsingFailed;
                match ctx {
                    nom::Context::Code(pos, kind) => {
                        error_reason = format!("parsing failed at byte {:?}: parser {:?}", pos, kind).as_str();
                    },
                    nom::Context::List(errors) => {
                        error_reason = errors.iter()
                            .map(|(pos, kind)| format!("parsing failed at byte {:?}: parser {:?}", pos, kind))
                            .collect::<Vec<String>>()
                            .join(": ");
                        error = Some(errors);
                    }
                }
            },
        }
        EasyError{
            kind: error_kind,
            reason: error_reason,
            error
        }
    }
}

impl std::fmt::Display for EasyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, format!("Error Kind: {:?}, Error: {:?}", self.kind, self.reason))
    }
}


impl std::error::Error for EasyError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(&self.err)
    }
}

